Перейти к основному содержимому

7.05. Справочник по gRPC

Разработчику Архитектору Инженеру

Справочник по gRPC

1. Общая структура .proto-файла

Прототип .proto-файла в proto3 (текущая рекомендованная версия):

syntax = "proto3";

package example.v1;

import "google/protobuf/timestamp.proto";
import "google/api/annotations.proto";

option go_package = "example/v1;examplev1";
option java_multiple_files = true;
option java_package = "com.example.v1";
option java_outer_classname = "ExampleProto";
option csharp_namespace = "Example.V1";
option objc_class_prefix = "EXM";
option optimize_for = SPEED;

// Внешние определения (custom options, расширения — только proto2)
// extend google.protobuf.MessageOptions { ... }

message MyRequest {
string user_id = 1;
int32 page = 2;
}

message MyResponse {
repeated string items = 1;
google.protobuf.Timestamp created_at = 2;
}

service ExampleService {
rpc GetItems(MyRequest) returns (MyResponse) {
option (google.api.http) = {
get: "/v1/items"
};
}
}

2. Директивы верхнего уровня

ДирективаОписаниеПоддержка
syntax = "proto2" / "proto3"Указывает версию протобафа. Обязательна.proto2/proto3
package <name>Логическое пространство имён. Влияет на имена классов в кодогенерации.Обе
import "<path>"Импорт других .proto. Импортируемые файлы должны быть доступны в --proto_path.Обе
option <name> = <value>;Глобальные (файловые) опции.Обе
extend ...Расширения сообщений (только proto2, deprecated в proto3).Только proto2

Основные file-level options

ОпцияТипЗначение по умолчанию / примечаниеГде влияет
java_packagestringНе задан → берётся package. Рекомендуется явно указывать.Java-генерация
java_outer_classnamestringБазовое имя файла + OuterClass.Java — имя внешнего класса-контейнера
java_multiple_filesboolfalsetrue → каждый message/service как отдельный класс
java_string_check_utf8boolfalsetrue → проверка корректности UTF-8 при парсинге строк
optimize_forenum (SPEED, CODE_SIZE, LITE_RUNTIME)SPEEDВлияет на объём и скорость сгенерированного кода (proto2/proto3)
go_packagestringНе задан → package. Формат: "path;alias"Go — путь импорта и псевдоним пакета
csharp_namespacestringНе задан → package.C# — namespace
objc_class_prefixstringОбязателен в proto3 для Objective-C. Должен быть 3+ буквы, уникальный.Swift/ObjC
php_namespacestringНе задан → package.PHP
php_metadata_namespacestringPHP: где хранить метаданные
php_class_prefixstringPHP: префикс имён классов
ruby_packagestringRuby
swift_prefixstringSwift
deprecatedboolfalseПомечает файл как устаревший (влияет на генерируемый код, если поддерживается языком)

⚠️ Некоторые опции (например, cc_api_version, cc_generic_services) устарели или не поддерживаются в proto3 (в частности, generic services отключены по умолчанию и deprecated).


3. Сообщения (message)

Базовая структура

message User {
option (my_file_options).id = 123;
option deprecated = true;

string email = 1 [(validate.rules).string.email = true];
int32 age = 2 [jstype = JS_STRING];
oneof contact_info {
string phone = 3;
string telegram = 4;
}
repeated string tags = 5 [packed = true];
map<string, string> metadata = 6;
}
3.1. Поля сообщения

Формат поля:

[field_behavior] [field_type] field_name = field_number [field_options];
КомпонентОбязательный?Описание
field_behaviorНет (только proto3)optional (proto3), required (proto2 only), repeated
field_typeДаscalar, message, enum, map<...>, oneof-члены
field_nameДаsnake_case по convention (но не enforced)
field_numberДаint ≥ 1. 1–15 — 1 байт в wire format, 16–2047 — 2 байта. Зарезервированы 19000–19999 (wire type tags).
field_optionsНет[key = value, ...]
3.1.1. field_behavior
Значениеproto2proto3Примечание
requiredDeprecated даже в proto2 (начиная с Protobuf 3.3.0; генерирует warnings). Избегать.
optional✅ (по умолчанию)✅ (явно с proto 3.12+, включено по умолчанию с 3.15)В proto3: включает presence tracking (has_field).
repeatedДля скаляров в proto3: packed=true по умолчанию.

🔹 В proto3 до 3.12: optional нельзя было писать (но optional-поля существовали в runtime через HasField). С 3.15 — optional включён по умолчанию (т.е. optional int32 x = 1; допустим).

🔹 required считается антипаттерном: нарушает forward/backward совместимость (удаление → поломка парсинга).

3.1.2. Scalar types
.proto typeC++JavaPythonGoC#Dartwire typeJSON encode
doubledoubledoublefloatfloat64doubledouble1number
floatfloatfloatfloatfloat32floatdouble5number
int32int32intintint32intint0number
int64int64longintint64longint0string¹
uint32uint32intintuint32uintint0number
uint64uint64longintuint64ulongint0string¹
sint32int32intintint32intint0number
sint64int64longintint64longint0string¹
fixed32uint32intintuint32uintint5number
fixed64uint64longintuint64ulongint1string¹
sfixed32int32intintint32intint5number
sfixed64int64longintint64longint1string¹
boolboolbooleanboolboolboolbool0true/false
stringstringStringstr/unicodestringstringString2string
bytesstringByteStringbytes[]byteByteStringUint8List2base64 string

¹ — Для int64, uint64, fixed64, sfixed64 в JSON: строка, чтобы избежать потери точности в JS (Number.MAX_SAFE_INTEGER = 2⁵³−1). Это регулируется опцией jstype (см. ниже).

3.1.3. jstype (field option, proto3 only)

Управляет JSON-представлением 64-битных типов:

int64 id = 1 [jstype = JS_STRING];   // → "1234567890123456789"
int64 id = 1 [jstype = JS_NUMBER]; // → 1234567890123456789 (потенциально unsafe!)
int64 id = 1 [jstype = JS_NORMAL]; // → как по умолчанию (строка)
ЗначениеJSONРиски
JS_NORMAL (default)"123"Безопасно
JS_STRING"123"Безопасно, явно
JS_NUMBER123Потеря точности в JS при значении > 2⁵³−1

4. Составные типы полей

4.1. oneof

Группа полей, из которых только одно может быть установлено в один момент времени. Используется для union-подобной семантики.

Синтаксис
message LoginRequest {
oneof method {
string username = 1;
string email = 2;
int64 phone = 3;
}
string password = 4;
}
Особенности
СвойствоОписание
Имя oneofОпционально. Без имени — анонимный oneof (не рекомендуется). Имя используется в сгенерированном коде (WhichOneof("method")).
Поля внутриЛюбые типы, включая message, enum, scalar. Но не repeated, map, group (deprecated).
Field numbersОбщее пространство с другими полями сообщения — не должны пересекаться.
Наследованиеoneof не может быть унаследован (в protobuf нет наследования сообщений).
КодогенерацияВ большинстве языков создаётся enum-case (например, MethodOneofCase в C#/Java) и setter/getter методы (setUsername(), getEmail() и т.п.).
ОчисткаУстановка нового поля внутри oneof автоматически сбрасывает предыдущее.
JSONСериализуется как обычные поля. При десериализации: последнее установленное значение побеждает (если несколько — поведение неопределено).
proto2/proto3Поддерживается в обеих версиях.
Опции oneof (редко используются, но доступны)
  • Нет официальных field/message options для oneof напрямую.
  • Можно применить custom options к отдельным полям внутри oneof.
Пример использования в коде (C# с Google.Protobuf)
var req = new LoginRequest { Email = "user@example.com" };
Console.WriteLine(req.MethodCase); // LoginRequest.MethodOneofCase.Email
req.Username = "admin";
Console.WriteLine(req.MethodCase); // LoginRequest.MethodOneofCase.Username

4.2. map<K, V>

Ассоциативный массив. Компилируется в хеш-таблицы в целевых языках.

Синтаксис
map<string, string> metadata = 1;
map<int32, User> user_cache = 2;
map<string, google.protobuf.Value> dynamic_data = 3;
Ограничения
ПараметрТребования
K (ключ)Только string, int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool. Не float, double, bytes, message, enum.
V (значение)Любые типы, включая message, enum, oneof-содержащие сообщения. Но не map, repeated, group.
Field numberОбычное поле. Занимает один номер. Внутренне реализован как repeated message с полями key, value.
Wire format

Внутренне эквивалент:

message MapFieldEntry {
option map_entry = true;
optional K key = 1;
optional V value = 2;
}
repeated MapFieldEntry map_field = N;

🔹 map_entry = true — служебная опция, задаётся автоматически компилятором.

Поведение
АспектКомментарий
ПорядокНе гарантируется (как и в хеш-таблицах).
Повторяющиеся ключиПри парсинге: последнее значение побеждает.
Производительностьmap эффективнее repeated KeyValue, но менее гибок (нельзя добавить third-party metadata к записи).
СовместимостьМожно безопасно изменить тип поля с repeated KeyValuemap, если структура KeyValue соответствует key/value. Обратное — нет.
proto2/proto3Доступен только в proto3+. В proto2 — только через repeated message с map_entry=true.

4.3. repeated

Список/массив значений.

Синтаксис
repeated string tags = 1;
repeated User friends = 2;
Опции
ОпцияТипЗначение по умолчанию (proto3)Примечание
packedbooltrue для скалярных типовВлияет на wire format: упаковка в один length-delimited chunk (экономия места). Для message/enum — всегда false (нельзя). Устаревшая, но допустимая конструкция: [packed = false].
Особенности
АспектПодробности
Пустой списокНе передаётся по сети (экономия bandwidth). При десериализации — пустая коллекция (не null).
Null-элементыНедопустимы. Все элементы не-nullable.
Производительностьrepeated message — эффективнее, чем map при известном ключе (если ключ — индекс или enum).
Обратная совместимостьБезопасно: добавление repeated поля не ломает старые клиенты (игнорируют). Удаление — тоже безопасно, если поле не критично.

5. enum

Перечисления.

Базовый синтаксис

enum StatusCode {
STATUS_UNKNOWN = 0;
STATUS_OK = 200;
STATUS_NOT_FOUND = 404;
}

message Response {
StatusCode code = 1;
}

Правила и ограничения

ПравилоЗначение
Первое значениеДолжно быть = 0. В proto3 — это значение по умолчанию для неинициализированных полей. В proto2 — допустимо указать option allow_alias = true, чтобы разрешить дубли значений (но не имён).
Значения32-битные signed int. Диапазон: −2³¹ … 2³¹−1.
ИменаUPPER_SNAKE_CASE по convention.
РасширенияВ proto2: extend StatusCode { ... } (deprecated). В proto3 — нельзя.
ReservedМожно резервировать значения/имена: reserved 10, 11 to 15; reserved "OLD_CODE";

Опции enum и enum-value

УровеньОпцияТип
enum-levelallow_aliasbool (только proto2)
enum-leveldeprecatedbool
value-leveldeprecatedbool
value-leveloption (my_options).weight = 5;custom option

Совместимость

  • Добавление нового значения enum — безопасно (старые клиенты получат неизвестное значение → в proto3: UNRECOGNIZED, в proto2: UNKNOWN_ENUM_VALUE_StatusCode_999).
  • Удаление значения — небезопасно, если оно использовалось в сериализованных данных.
  • Изменение числового значения — категорически запрещено.

JSON mapping

ПолеЗначениеJSON
code = STATUS_OK200"STATUS_OK" (по имени) или 200 (по числу), в зависимости от настроек сериализатора.
Неизвестное значение (999)"UNKNOWN_STATUS_CODE_999" (или число, если enum_as_int).

🔹 В gRPC-JSON трансляторах (например, grpc-gateway) по умолчанию используется имя, а не число.


6. reserved

Запрещает использование определённых имён или номеров полей/значений enum — для предотвращения несовместимых изменений.

Применение

message User {
reserved 2, 15, 9 to 11;
reserved "login", "password_hash";

string email = 1;
// int32 age = 2; ← ошибка компиляции: номер 2 зарезервирован
// string login = 3; ← ошибка: имя "login" зарезервировано
}

Где можно использовать

КонтекстСинтаксисПример
messagereserved <field_number>, <range>, "<name>";reserved 4, 5 to 7, "old_field";
enumreserved <value>, <range>, "<name>";reserved 500, 501 to 599, "DEPRECATED_CODE";

Важно

  • Нельзя резервировать пересекающиеся диапазоны.
  • Резервирование не влияет на wire format — только на этапе компиляции .proto.
  • Необратимо: однажды зарезервированное нельзя "разрезервировать" без поломки совместимости.

7. Extensions (только proto2, deprecated)

Механизм для расширения сообщений без изменения их исходного определения.

Синтаксис (proto2)

// base.proto
message BaseMessage {
extensions 100 to 199;
}

// ext.proto
extend BaseMessage {
optional string extended_field = 101;
}

Использование в коде (Java, proto2)

BaseMessage msg = BaseMessage.newBuilder()
.setExtension(Ext.extendedField, "value")
.build();
String val = msg.getExtension(Ext.extendedField);

Почему deprecated

  • Нарушает читаемость: структура сообщения не видна из .proto.
  • Плохо масштабируется.
  • Не поддерживается в proto3 (кроме Any, см. ниже).
  • Не поддерживается большинством современных фреймворков (например, gRPC Gateway, Envoy, Linkerd).

✅ Альтернатива: google.protobuf.Any (см. раздел Well-Known Types).


8. Well-Known Types (google.protobuf.*)

Стандартные типы, поставляемые с protobuf. Импортируются как:

import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
// ...
ТипОписаниеПример использования
AnyУпаковка любого сообщения по type_url (например, type.googleapis.com/example.v1.User).Полиморфные API, события, generic payload.
TimestampТочное время с точностью до наносекунд (RFC 3339).created_at, last_modified.
DurationПродолжительность (например, 3.5s, 2d).Таймауты, ttl, интервалы.
Struct, Value, ListValue, NullValueДинамические JSON-подобные структуры.Конфиги, метаданные, API с динамической схемой.
FieldMaskЧастичное обновление (update_mask в UpdateRequest).PATCH-операции, selective fetch.
EmptyПустое сообщение (аналог void).Ping(), Delete() без входных данных.
DoubleValue, FloatValue, Int64Value, UInt32Value, ... (wrapper types)Обёртки для nullable скаляров (optional до proto 3.12).repeated google.protobuf.Int32Value items; где null допустим.
Api, Method, Syntax, SourceContextОписание API (для инструментов).Интроспекция, генерация документации.
Type, Enum, EnumValue, OptionМетаописание protobuf-схемы.Рефлексия, динамический парсинг.

Пример: Any

import "google/protobuf/any.proto";

message Event {
string event_id = 1;
google.protobuf.Any payload = 2;
}

В коде (C#):

var user = new User { Id = "u1" };
var any = Any.Pack(user); // type_url = "type.googleapis.com/example.v1.User"
var evt = new Event { Payload = any };

// Распаковка
if (evt.Payload.Is(User.Descriptor)) {
var unpacked = evt.Payload.Unpack<User>();
}

⚠️ Any требует, чтобы получатель знал схему упакованного типа (или имел DescriptorPool). Не подходит для полностью динамических систем без реестра типов.


9. Сервисы и методы

9.1. Объявление сервиса

service UserService {
option (google.api.default_host) = "api.example.com";
option (google.api.oauth_scopes) = "https://www.googleapis.com/auth/userinfo.email";

rpc GetUser(GetUserRequest) returns (User) {
option (google.api.http) = {
get: "/v1/users/{user_id}"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Получить пользователя по ID";
description: "Возвращает полную информацию о пользователе.";
tags: "Users";
};
}

rpc StreamEvents(StreamEventsRequest) returns (stream Event) {}
rpc UploadAvatar(stream AvatarChunk) returns (AvatarMetadata) {}
rpc Chat(stream Message) returns (stream Message) {}
}

9.2. Типы методов

ТипСигнатураНазначениеПримеры
Unaryrpc Method(Request) returns (Response)Один запрос → один ответ.GetUser, CreateOrder, Ping
Server-Streamingrpc Method(Request) returns (stream Response)Один запрос → поток ответов.WatchChanges, SubscribeToNotifications, ListLogEntries
Client-Streamingrpc Method(stream Request) returns (Response)Поток запросов → один ответ.UploadFile, BatchInsert, CollectMetrics
Bidirectional Streamingrpc Method(stream Request) returns (stream Response)Полный duplex.Chat, CollaborativeEditing, RealtimeAnalytics
Особенности работы со streaming:
  • Все streaming-методы требуют явного управления потоком (backpressure, cancellation).
  • Протокол: HTTP/2, frame-based (DATA, HEADERS, RST_STREAM).
  • Сервер обязан завершать поток вызовом SendAndClose (unary) или Send/CloseSend + Recv в цикле (streaming).
  • Клиент может отменить вызов через CancellationToken (C#), Context (Go), AbortController (JS/TS).

10. Опции методов и сервисов

10.1. Стандартные и широко используемые опции

ОпцияУровеньКонтекстОписание
(google.api.http)methodannotations.protoСопоставление gRPC ↔ HTTP/JSON (REST-шлюз). Поддерживает get, post, put, patch, delete, body, additional_bindings.
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation)methodprotoc-gen-openapiv2Переопределения для OpenAPI/Swagger (summary, description, tags, parameters, responses).
(google.api.method_signature)methodannotations.protoНабор полей, формирующих «сигнатуру» (для генерации методов в SDK, напр., get_user(user_id) вместо get_user(request)).
(google.api.resource_definition) / (google.api.resource)file/messageresource.protoОписание иерархии ресурсов (например, projects/{project}/locations/{location}/clusters/{cluster}).
(google.api.field_behavior)fieldfield_behavior.protoСемантика поля: REQUIRED, OUTPUT_ONLY, INPUT_ONLY, IMMUTABLE, OPTIONAL. Используется валидаторами и генераторами документации.
(grpc.method_idempotency_level)methodgRPC coreIDEMPOTENT / NO_SIDE_EFFECTS / UNKNOWN. Влияет на retry-поведение (если IDEMPOTENT, клиент может повторить при UNAVAILABLE).
(validate.rules)field/messageprotoc-gen-validateПравила валидации: string.min_len, int.gt, email, ipv4, uuid, contains, in, pattern и др.
Пример: HTTP mapping + валидация + OpenAPI
import "google/api/annotations.proto";
import "protoc-gen-validate/validate.proto";
import "grpc/gateway/protoc_gen_openapiv2/options/annotations.proto";

message CreateUserRequest {
string email = 1 [
(validate.rules).string = {
email: true,
min_len: 5,
max_len: 254
},
(google.api.field_behavior) = REQUIRED
];
string name = 2 [(validate.rules).string.min_len = 1];
}

rpc CreateUser(CreateUserRequest) returns (User) {
option (google.api.http) = {
post: "/v1/users"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Создать пользователя";
description: "Регистрирует нового пользователя. Email должен быть уникальным.";
tags: "Users";
responses: {
key: "409"
value: {
description: "Пользователь с таким email уже существует.";
schema: {
json_schema: { ref: "#/definitions/google.rpc.Status" }
}
}
};
};
}

11. Соглашения по именованию и структуре (AIP, Google API Style Guide)

⚠️ Не являются частью протокола, но критически важны для совместимости, документации и инструментария.

11.1. Имена пакетов и файлов

ЭлементРекомендацияПример
packagelowercase, . как разделитель, версия в конце (v1, v1alpha1, v1beta1)example.user.v1
Файлlower_snake_case, .proto, группа по домену/версииuser_service.proto, user_resource.proto
ИмпортОрганизован по слоям: стандартные → well-known → внешние → локальныеgoogle/...third_party/...example/...

11.2. Имена сообщений и полей

ЭлементРекомендацияПримечание
СообщениеUpperCamelCase, существительное или прилагательное + Request/Response/MetadataCreateUserRequest, ListUsersResponse, User
Полеsnake_case, кратко и однозначноuser_id, created_at, display_name
oneofsnake_case, отражает семантикуcontact_info, auth_method
EnumUPPER_SNAKE_CASE, префикс с именем enum (избегать конфликтов)STATUS_OK, CODE_INVALID_ARGUMENT

11.3. Стандартные сообщения (AIP-131/132/133/134/135)

ОперацияРекомендуемое имя методаRequestResponse
GetGet{Resource}{Resource}Namestring name{Resource}
ListList{Resources}string parent, int32 page_size, string page_tokenrepeated {Resource} {resources}, string next_page_token
CreateCreate{Resource}string parent, {Resource} {resource}, string {resource}_id{Resource}
UpdateUpdate{Resource}{Resource} {resource}, FieldMask update_mask{Resource}
DeleteDelete{Resource}string namegoogle.protobuf.Empty
Повторная отправка (replay-safe)Добавить string request_id в Request

🔹 name — каноническое имя ресурса: projects/123/users/456.


12. Кастомные опции (custom options)

Позволяют расширять .proto схемы аннотациями для генераторов, линтеров, runtime.

12.1. Объявление кастомной опции

// my_options.proto
syntax = "proto3";

import "google/protobuf/descriptor.proto";

extend google.protobuf.FieldOptions {
string my_field_tag = 50001;
}

extend google.protobuf.MethodOptions {
bool my_method_audit = 50002;
}

12.2. Использование

import "my_options.proto";

message User {
string email = 1 [(my_field_tag) = "PII"];
}

service UserService {
rpc GetUser(GetUserRequest) returns (User) {
option (my_method_audit) = true;
}
}

12.3. Практическое применение

ЦельИнструментПример опций
Валидацияprotoc-gen-validate(validate.rules).string.email = true
OpenAPIprotoc-gen-openapiv2(grpc.gateway...)
АвторизацияCustom middleware(my.auth).roles = ["admin", "support"]
ЛоггированиеCustom interceptor(my.log).level = "INFO"
ИдемпотентностьClient generator(my.idempotency).key_field = "request_id"
МетрикиObservability SDK(my.metrics).name = "user_get_latency"

⚠️ Номера расширений (50001+) должны быть уникальны в рамках проекта. Рекомендуется регистрировать в центральном реестре (например, internal/api-options.proto).


13. Эволюция схемы: правила обратной и прямой совместимости

13.1. Безопасные изменения (forward & backward compatible)

Изменениеproto2proto3Примечание
Добавить optional/repeated полеСтарые клиенты игнорируют. Новые — получают default/empty.
Добавить oneof-полеСтарые клиенты видят 0/"".
Добавить mapВнутренне — repeated message.
Добавить enum-значение✅*✅** — клиенты должны обрабатывать UNRECOGNIZED.
Изменить optionalrepeated⚠️Только если поле не использовалось ранее.
Добавить reservedТолько для будущего использования.

13.2. Небезопасные изменения (ломают совместимость)

ИзменениеПоследствие
Изменить номер поляНеправильная десериализация (поле A читается как B).
Изменить тип поля (например, stringint32)InvalidProtocolBufferException, UnmarshallingError.
Удалить required поле (proto2)Крах парсинга у клиентов, отправляющих старый формат.
Переименовать enum-значение без резервирования старогоСтарые клиенты отправляют старое имя → UNKNOWN.

13.3. Стратегии миграции

  • Двойная запись / двойное чтение (dual writing / dual reading).
  • Версионирование API: v1, v1beta1, v2.
  • FieldMask: клиенты явно указывают, какие поля хотят читать/писать.
  • oneof для альтернативных форматов:
    message Request {
    oneof version {
    LegacyData v1 = 1;
    NewData v2 = 2;
    }
    }

14. Обработка ошибок

14.1. Стандартный статус gRPC (grpc.Status)

Код (int)ИмяHTTP-маппингКогда использовать
0OK200Успех.
1CANCELLED499Клиент отменил.
2UNKNOWN500Неизвестная ошибка (логгировать!).
3INVALID_ARGUMENT400Некорректный запрос (валидация).
4DEADLINE_EXCEEDED504Таймаут.
5NOT_FOUND404Ресурс не найден.
6ALREADY_EXISTS409Конфликт уникальности.
7PERMISSION_DENIED403Нет прав (не аутентификация!).
8RESOURCE_EXHAUSTED429Лимиты (rate, quota, memory).
9FAILED_PRECONDITION400Нарушено предусловие (например, update_mask без полей).
10ABORTED409Конфликт транзакции (CAS failed).
11OUT_OF_RANGE400Выход за границы (например, page_size > 1000).
12UNIMPLEMENTED501Метод не реализован.
13INTERNAL500Ошибка сервера (баг).
14UNAVAILABLE503Сервис недоступен (retryable).
15DATA_LOSS500Потеря данных (редко).
16UNAUTHENTICATED401Не аутентифицирован.

14.2. Детализация ошибки: google.rpc.Status

message Status {
int32 code = 1;
string message = 2;
repeated google.protobuf.Any details = 3;
}

Пример details:

  • google.rpc.BadRequest — указание на конкретное поле.
  • google.rpc.QuotaFailure — какая квота исчерпана.
  • google.rpc.PreconditionFailure — какие условия нарушены.
  • Кастомное сообщение — для внутренних нужд.

14.3. Передача ошибок в клиентах

  • C#: RpcException.StatusCode, RpcException.Trailers, RpcException.Status.Details.
  • Go: status.FromError(err), st.Details().
  • Java: StatusRuntimeException.getStatus().
  • Python: grpc.RpcError.code(), details.

15. Кодогенерация: инструменты, плагины и стратегии

15.1. Базовый инструментарий

ИнструментНазначениеВерсияОсобенности
protocОфициальный компилятор Protocol Buffers≥ v3.0 (proto3), ≥ v21.0 (proto3+/edition2023)Требует явной установки protoc-gen-* плагинов. Не имеет встроенного управления зависимостями.
bufУниверсальный инструмент для работы с protobuf (lint, format, generate, breaking, mod)≥ v1.0Включает встроенный protoc, управление модулями, CI-интеграция, строгая проверка совместимости.
protoc-gen-*Плагины для генерации кодаЯзык-зависимыеОфициальные: grpc_***_plugin (C++, Java, Python, Go, Ruby, C#, PHP, Dart, Node.js).
Минимальная команда protoc:
protoc \
--proto_path=src/proto \
--csharp_out=gen/csharp \
--csharp_opt=base_namespace=Example.V1 \
--grpc_out=gen/csharp \
--plugin=protoc-gen-grpc=/usr/local/bin/grpc_csharp_plugin \
src/proto/user_service.proto
Эквивалент в buf.gen.yaml:
version: v1
plugins:
- name: csharp
out: gen/csharp
opt:
- base_namespace=Example.V1
- name: grpc_csharp
out: gen/csharp
path: grpc_csharp_plugin

buf generate

15.2. Популярные генераторы (альтернативы официальным)

ГенераторЯзыкПлюсыМинусыСовместимость
gRPC-Web (protoc-gen-grpc-web)TS/JSРаботает в браузере через HTTP/1.1 (proxy required)Требует envoy/gateway, нет streaming в браузере (только unary)Совместим с gRPC-серверами
connect-go / connect-web (Buf)Go/TSСовместимость с gRPC и Connect (Protocol + JSON), поддержка streaming в браузере (via WebSockets или HTTP/2), меньше boilerplateМенее «каноничный» для чистого gRPCЧастично обратно совместим
betterproto (Python)PythonTyped dataclasses, async-first, меньше boilerplateНе 100% совместим с grpcio APIСовместим на wire level
ts-protoTypeScriptПоддержка NestJS, TypeORM, strict types, enums as union typesТребует отдельной настройки для сервераСовместим на wire level
protoc-gen-validateМультиязыковойRuntime validation (Go, Java, Python, JS, C++)Добавляет зависимость, накладные расходы на валидациюЯвляется extension, не заменяет генератор

🔹 Рекомендация: для production-систем — официальные генераторы (google.golang.org/protobuf, Grpc.Tools в .NET, grpc-java). Альтернативы — при особых требованиях (браузер, строгая типизация).

15.3. Контроль версий схем: buf.lock и deps

buf поддерживает модули с фиксированными хешами:

// buf.yaml
version: v1
name: buf.build/example/user
deps:
- buf.build/googleapis/googleapis
- buf.build/grpc-ecosystem/grpc-gateway
breaking:
use:
- FILE
lint:
use:
- DEFAULT

buf mod update фиксирует buf.lock с SHA256-хешами зависимостей.

Это гарантирует повторяемость генерации: все разработчики и CI используют одинаковые версии googleapis, protoc, и т.д.


16. Транспорт и wire format

16.1. Форматы сериализации

ФорматMIME-TypeПоддержкаПримечания
Binary (proto)Все реализацииКомпактный, быстрый, binary-safe. Content-Type: application/grpc (подразумевает binary protobuf).
JSONapplication/jsongrpc-gateway, connect, envoyТребует google/api/http.proto, FieldMaskupdateMask (snake_case). 64-bit → строки по умолчанию.
TextFormatИнструменты (protoc --encode), отладкаЧеловекочитаемый, не для продакшена. Пример: user_id: "u1" created_at: { seconds: 1732000000 }.

16.2. Сжатие

АлгоритмHTTP-заголовокПоддержкаНакладные расходы
gzipgrpc-encoding: gzip, grpc-accept-encoding: gzipПочти все (C++, Go, Java, .NET)ЦП: ~10–20% CPU, выигрыш 60–80% размера.
deflategrpc-encoding: deflateЧастичнаяМенее эффективен, чем gzip.
snappygrpc-encoding: snappyGo, C++, Java (часто custom)Быстрый, но меньшая степень сжатия (~20–30%).
ОтсутствиеПо умолчаниюДля маленьких сообщений (<1 KB) — предпочтительно.

⚠️ Сжатие не применяется к streaming-фреймам по отдельности — оно действует на весь поток. Сервер и клиент должны согласовать алгоритм через grpc-accept-encoding.

16.3. HTTP/2 и multiplexing

  • gRPC обязательно требует HTTP/2.
  • Один TCP-соединение → множество параллельных вызовов (streams).
  • Управление потоком: SETTINGS_INITIAL_WINDOW_SIZE, WINDOW_UPDATE.
  • Keepalive: настраивается через grpc.keepalive_time_ms, grpc.http2.max_pings_without_data.
Keepalive параметры (клиент и сервер):
ПараметрЗначение по умолчаниюРекомендация prod
GRPC_ARG_KEEPALIVE_TIME_MSINT_MAX (отключено)300_000 (5 мин)
GRPC_ARG_KEEPALIVE_TIMEOUT_MS20_00020_000
GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS01 (если нужен health-check без вызовов)
GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA20 (ping только при активности)

🔹 Без keepalive: промежуточные прокси (nginx, ALB) могут разрывать «тихое» соединение через 60–300 сек.


17. Безопасность

17.1. Транспортный уровень

МеханизмОписаниеПоддержка
TLSШифрование каналаВсе реализации. Сертификат сервера обязателен.
mTLSДвусторонняя аутентификация (клиент + сервер)Требует ssl_client_cert, ssl_client_key у клиента.
ALTSApplication Layer Transport Security (Google Cloud only)grpc++_alts (C++), io.grpc.alts (Java). Не для публичных API.
Пример инициализации сервера (C#):
var cert = X509Certificate2.CreateFromPemFile("cert.pem", "key.pem");
var keyCertPair = new KeyCertificatePair(cert.Export(X509ContentType.Pem), cert.Export(X509ContentType.Pem));

var server = new Server
{
Services = { UserService.BindService(new UserServiceImpl()) },
Ports = { new ServerPort("0.0.0.0", 50051, new SslServerCredentials(new[] { keyCertPair })) }
};

17.2. Аутентификация и авторизация

УровеньМетодРеализация
ТранспортmTLS, TLS client certSslClientCredentials + custom interceptor для проверки CommonName/Subject.
МетаданныеAuthorization: Bearer <token>Перехват Context.RequestHeaders в interceptor.
JWTAuthorization: Bearer <JWT>Проверка подписи, issuer, exp, scope/roles в claims.
API Keyx-api-key: <key>Проверка в interceptor или gateway.
Interceptor (middleware) — пример (Go):
func AuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "no metadata")
}
tokens := md["authorization"]
if len(tokens) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing token")
}
// validate token → ctx = context.WithValue(ctx, "user", user)
return handler(ctx, req)
}

17.3. Шифрование данных (application-level)

ТребованиеРешение
PII в логахНе логировать metadata, request, response напрямую. Использовать маскировку (emailu***@e***.com).
Данные в базеШифрование на уровне приложения (AES-GCM) до сериализации в protobuf.
bytes-поляМогут содержать зашифрованные payload (например, encrypted_payload: bytes).

⚠️ Protobuf не предоставляет встроенной защиты данных. Шифрование — ответственность приложения.


18. Тестирование и отладка

18.1. Инструменты командной строки

ИнструментНазначениеПример
grpcurlcurl для gRPCgrpcurl -plaintext localhost:50051 list grpcurl -d '{"user_id":"u1"}' localhost:50051 UserService.GetUser
ghzНагрузочное тестированиеghz --proto user_service.proto --call UserService.GetUser --insecure localhost:50051
bloomrpcGUI-клиент (устаревает)
grpcuiВеб-интерфейс (на основе protoc reflection)grpcui -plaintext localhost:50051

18.2. Reflection и introspection

Сервер может поддерживать сервис grpc.reflection.v1alpha.ServerReflection:

service ServerReflection {
rpc ServerReflectionInfo(stream ServerReflectionRequest) returns (stream ServerReflectionResponse);
}

→ Позволяет клиентам динамически получать:

  • Список сервисов;
  • Описание методов;
  • .proto-файлы (если загружены в FileDescriptorSet).

Включение в Go:

import "google.golang.org/grpc/reflection"
reflection.Register(s)

В .NET:

services.AddGrpcReflection(); // в Startup
app.MapGrpcReflectionService(); // в Configure

18.3. Unit/integration тестирование

ЯзыкПодход
C#Grpc.AspNetCore.Server.ClientFactory, Grpc.Net.Client, TestServer + GrpcChannel.ForAddress(server.BaseAddress)
Gobufconn, grpc.NewServer(), grpc.DialContext(ctx, "bufnet", ...))
JavaInProcessServerBuilder, InProcessChannelBuilder
Pythongrpc.insecure_channel("localhost:0"), server.add_insecure_port("[::]:0")
Пример (C#):
[Fact]
public async Task GetUser_ReturnsUser()
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddGrpc();
var app = builder.Build();
app.MapGrpcService<UserServiceImpl>();
using var server = app.Services.GetRequiredService<IServer>();
var channel = GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions
{
HttpClient = new HttpClient(new TestHttpMessageHandler(app))
});
var client = new UserService.UserServiceClient(channel);
var resp = await client.GetUserAsync(new GetUserRequest { UserId = "u1" });
Assert.Equal("u1", resp.User.Id);
}

19. Производительность и тюнинг

19.1. Настройки вызова (CallOptions / CallCredentials)

ПараметрУровеньПримечание
Deadline / TimeoutКлиентwithDeadlineAfter(5, SECONDS) (Java), deadline: DateTime.UtcNow.AddSeconds(5) (C#). Сервер получает Context.Deadline. Превышение → DEADLINE_EXCEEDED.
CancellationКлиентCancellationToken (C#), Context.WithCancel() (Go). Сервер получает Context.IsCancellationRequested.
CompressionКлиент/СерверCallOptions.WithCompression(Compression.Gzip). Сервер должен поддерживать.
WaitForReadyКлиентCallOptions.WithWaitForReady(true). Не завершать вызов при UNAVAILABLE (например, при старте сервера). Использовать осторожно — может привести к зависанию.
MaxInboundMessageSizeСервер/КлиентMaxReceiveMessageSize (C#), MaxInboundMessageSize (Go). Защита от OOM. По умолчанию: 4 MB.
MaxOutboundMessageSizeКлиент/СерверАналогично.

19.2. Streaming: управление потоком и batching

ПроблемаРешение
BackpressureИспользовать Send/Recv с await (async) или SendAsync (C# streaming). Не отправлять быстрее, чем клиент читает.
ChunkingДля больших данных (файлы): фиксированный размер чанка (например, 64 KB). Избегать repeated bytes → использовать stream Chunk.
Batching ответовНа сервере: буферизовать N элементов или T мс, затем Send(). Снижает overhead фреймов.
Flow control (HTTP/2)Настройка initial_window_size, max_concurrent_streams на стороне сервера (nginx: http2_max_concurrent_streams, http2_chunk_size).

19.3. Пулы и ресурсы

КомпонентРекомендации
gRPC-канал (Channel)Создавать один канал на endpoint и переиспользовать. Дорогая инициализация (TLS handshake, HTTP/2 setup). В .NET: GrpcChannel.ForAddress(...) — singleton.
Серверные потокиНастройка SynchronizationContext (C#), runtime.GOMAXPROCS (Go), grpc.nettySharedGroups (Java).
СериализацияКэшировать Parser<T> (Java), MessageParser<T> (C#). Избегать повторного парсинга Any.
ReflectionОтключать в продакшене (reflection.Register только в dev/staging).

🔹 Пример пула каналов (C#):

services.AddSingleton<GrpcChannel>(sp =>
GrpcChannel.ForAddress("https://api.example.com", new GrpcChannelOptions
{
Credentials = SslCredentials.Default,
MaxReceiveMessageSize = 100 * 1024 * 1024, // 100 MB
MaxSendMessageSize = 100 * 1024 * 1024
}));

20. Наблюдаемость (Observability)

20.1. Tracing (OpenTelemetry)

gRPC интегрируется с OpenTelemetry через interceptor.

ЯзыкПакетПримечание
C#OpenTelemetry.Instrumentation.GrpcNetClient, Grpc.AspNetCore.ServerАвтоматически трассирует unary-вызовы. Streaming — частично.
Gogo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpcUnary и streaming.
Javaio.opentelemetry.instrumentation:opentelemetry-grpc-1.6Поддержка gRPC ≥1.6.
Данные трейса:
  • Span name: grpc.client.request / grpc.server.request
  • Атрибуты: rpc.system=grpc, rpc.service=UserService, rpc.method=GetUser, net.peer.ip
  • События: message sent, message received

20.2. Metrics (Prometheus)

МетрикаТипОписание
grpc_server_started_totalcounterВсего вызовов (по методу, статусу).
grpc_server_handled_totalcounterЗавершённых вызовов.
grpc_server_msg_received_totalcounterПринятых сообщений (streaming).
grpc_server_msg_sent_totalcounterОтправленных сообщений.
grpc_server_handling_secondshistogramВремя обработки (P50, P95, P99).
Экспорт в .NET:
services.AddOpenTelemetry()
.WithMetrics(builder => builder
.AddMeter("Grpc.AspNetCore.Server")
.AddPrometheusExporter());
app.MapPrometheusScrapingEndpoint("/metrics");

20.3. Логгирование

УровеньЧто логироватьФормат
DEBUGВходящий/исходящий запрос (для отладки)Structured: { method: "UserService.GetUser", request: {...}, duration_ms: 12 }
INFOЗавершённые unary-вызовы{ method, status, duration_ms, user_id }
WARNDEADLINE_EXCEEDED, UNAVAILABLE (не retryable){ method, status, attempt }
ERRORINTERNAL, UNKNOWN, DATA_LOSS{ method, status, stack_trace }

⚠️ Никогда не логировать:

  • metadata (может содержать токены);
  • поля с field_behavior = INPUT_ONLY / SENSITIVE;
  • содержимое bytes, Any, Struct без маскировки.

21. Развёртывание и инфраструктура

21.1. Прокси и шлюзы

КомпонентНазначениеПоддержка gRPC
EnvoyL4/L7 proxy, rate limiting, retriesПолная: transcoding (REST → gRPC), health checks (/grpc.health.v1.Health/Check), stats.
grpc-gatewayГенерация REST-прокси из .protoUnary ↔ REST, streaming → SSE/WebSocket (ограниченно).
nginxIngressПоддержка gRPC с 1.13.10+: grpc_pass grpc://backend;
Linkerd / IstioService meshАвтоматический mTLS, retries, timeouts, circuit breaking.
Пример Envoy config (transcoding):
routes:
- match: { prefix: "/v1/users/" }
route: { cluster: user_service }
typed_per_filter_config:
envoy.filters.http.grpc_json_transcoder:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
proto_descriptor: "/data/user_service.pb"
services: ["example.v1.UserService"]
print_options: { always_print_primitive_fields: true }

21.2. Health checks

Стандартный сервис grpc.health.v1.Health:

service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
  • Check: unary, для readiness/liveness probes.
  • Watch: streaming, для клиентов, отслеживающих состояние.

🔹 Kubernetes:

livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
readinessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051", "-service=UserService"]

21.3. CI/CD и проверка совместимости

ИнструментКомандаЭффект
buf breakingbuf breaking --against '.git#branch=main'Проверяет, не сломана ли совместимость с main.
buf lintbuf lintСоблюдение стиля (AIP, custom rules).
protolockprotolock statusLegacy-альтернатива buf breaking.

22. Миграция с REST/SOAP и гибридные API

22.1. Стратегии миграции

ЭтапДействие
1. Dual APIОдновременная поддержка REST и gRPC (например, через grpc-gateway).
2. Клиентская либаSDK с unified API (.GetUser(id) → gRPC или REST по флагу).
3. Внутреннее gRPCВнешний REST, внутренние вызовы — gRPC.
4. Полный переходТолько gRPC + gRPC-Web для фронтенда.

22.2. Преимущества гибридного подхода

  • Постепенный переход без downtime.
  • Возможность оценить latency/bandwidth/нагрузку.
  • Поддержка legacy-клиентов (mobile SDK v1).

22.3. Проблемы

  • Дублирование логики (валидация, авторизация).
  • Сложность поддержки двух схем (OpenAPI + .proto).
  • Разный набор фич (например, streaming недоступен в REST).

🔹 Решение: единственный источник истины — .proto. OpenAPI генерируется из него (protoc-gen-openapiv2).


23. Анти-паттерны и частые ошибки

ПроблемаПоследствиеИсправление
required в proto3Синтаксическая ошибкаИспользовать optional + валидацию.
Field number reuseНарушение wire-совместимостиВсегда reserved старые номера.
Отсутствие request_idНевозможность идемпотентностиДобавить string request_id = 999 во все Request.
Большое сообщение (>4 MB)RESOURCE_EXHAUSTEDРазбивать на stream, увеличивать MaxReceiveMessageSize.
Нет keepaliveРазрыв соединений проксиНастроить GRPC_ARG_KEEPALIVE_TIME_MS.
Синхронный streamingБлокировка потока, низкая пропускная способностьИспользовать async/await, backpressure.
Логгирование metadataУтечка токеновФильтровать authorization, x-api-key в логгере.
Отсутствие FieldMask в UpdateПерезапись всех полейТребовать update_mask, использовать mergeFrom.
Смешивание proto2/proto3Неочевидные default-значенияСтандартизировать на proto3 (или edition 2023).